C#网络编程

USB HID 报告描述符

作者:陈广 日期:2019-6-12


本文主要是为 ST25 开发板准备的内容,属单片机端的开发,而不是上位机。一方面,ST25 开发板的零件还没有到货,没办法焊接进行开发;另一方面,这一块内容在开发板中只是用于取代串口与上位机进行通信调试,与 ST25 关系不大,所以放在这里更为适合。

上一篇文章,已经讲解了上位机 HID 通信的实现,这一篇开始则讲单片机端 HID 的实现。而在下位机要掌握 HID,则必须首先掌握 USB HID 报告描述符。

USB通信与串口通信相比,复杂得多。但从硬件上来说,对于支持 USB 的芯片来说,其电路又相对简单,而且成本更为便宜。使用UART通信,必须使用 USB 转串口芯片,为保证通信质量,还得使用一个价格昂贵的坦电容。而 USB 通信只需几个电阻就搞定了。另一方面,USB 无需安装驱动,UART 通信则需要安装 CH340 驱动,这就很有吸引力了,我们有什么理由不使用 HID 呢。HID 通信最常使用的地方就是鼠标和键盘,另外条码的扫码枪以及部分 RFID 读卡器也使用 HID 通信,只要标注免驱的读卡器,其肯定是使用 HID 进行通信。

在 UART 通信中,信息是以字节为单位进行传送,下位机传一个字节,上位机接收一个字节,接收到多个字节后,上位机进行解析即可使用。而 HID 通信中,信息以数据包(报告 report)的方式进行传送,一个数据包可包含多个字节,你需要指定数据包中每个 Bit 及字节的格式及含义,这就是报告描述符。上位机根据报告描述符来解析数据包。鼠标和键盘的报告描述符是固定格式的,属于 HID 标准的一部分。这也是为什么大部分 USB 鼠标和键盘一插到你的电脑上就能使用的根本原因。说到这里,大家应该明白了,HID 是高级货,既然是高级货,那学习和使用起来就会困难一些。

HID Descriptor Tool

写 HID 报告描述符很让人头痛,因为相关协议复杂无比。让我去读这些协议?那是不可能的,这辈子都不可能的事。索性,USB协会写了一个 HID 报告描述符生成工具,大大简化了编写难度。下载地址:

https://www.usb.org/hid

这是 HID 官网,在此页面中找到HID Descriptor Tool,然后下载即可。

这是一个绿色软件,解压后找到Dt.ext文件双击执行即可。打开 HID Descriptor Tool 后,单击【File】菜单,选择【Open】项,在打开的文件夹中选择【mouse.hid】文件,即可打开标准鼠标的报告描述符。如下图所示:

图 1:鼠标报告描述符

接下来我们简要解释一下这个鼠标报告描述符。

鼠标报告描述符

一个标准的鼠标有三个按键和一个滚球组成。按键包括:左键、中键、右键;滚球则根据滚动速度和方向发出X和Y轴方向的位移数据。要包含这些状态,鼠标则需要每次发送长度为三个字节的数据包。组成如下图所示:

图 2:鼠标数据包结构

注意,左中右键只需记录是弹起还是按下状态,用一个 bit 即可,0表示弹起,1表示按下。

上述鼠标描述符就是讲解这三个字节是如何使用的。

首先,下述头尾部分文字表述这是一个鼠标设备,我们看到了第二行指定了 Mouse。更详细的信息这里就不讲了,我也没详细研究。大家可以上网搜索相关文章或查看 HID 协议,我的目标只是能写出想要的效果即可。

USAGE_PAGE (Generic Desktop)	05 01
USAGE (Mouse)	09 02
COLLECTION (Application)	A1 01 
  USAGE (Pointer)	09 01
  COLLECTION (Physical)	A1 00 
    .......
  END_COLLECTION	C0
END_COLLECTION	C0

中间部分则描述了鼠标向上位机传送的三个字节的含义:

USAGE_PAGE (Button)	05 09
USAGE_MINIMUM (Button 1)	19 01
USAGE_MAXIMUM (Button 3)	29 03
LOGICAL_MINIMUM (0)	15 00 
LOGICAL_MAXIMUM (1)	25 01 
REPORT_COUNT (3)	95 03 
REPORT_SIZE (1)	75 01 
INPUT (Data,Var,Abs)	81 02 
REPORT_COUNT (1)	95 01 
REPORT_SIZE (5)	75 05 
INPUT (Cnst,Var,Abs)	81 03 
USAGE_PAGE (Generic Desktop)	05 01
USAGE (X)	09 30
USAGE (Y)	09 31
LOGICAL_MINIMUM (-127)	15 81 
LOGICAL_MAXIMUM (127)	25 7F 
REPORT_SIZE (8)	75 08 
REPORT_COUNT (2)	95 02 
INPUT (Data,Var,Rel)	81 06 
  1. USAGE_PAGE (Button)表示一个数据描述的开始,所描述的数据用于按键。在左侧【HID Items】栏中双击【USAGE_PAGE】项时,弹出的窗口中选择【Button】项即可。
  2. USAGE_MINIMUM (Button 1)表示最小按键为Button 1。前面选择了【Button】,然后再单击【USAGE_MINIMUM】项时,窗口中则可以选择【Button 1】~【Button 63】。
  3. USAGE_MAXIMUM (Button 3)表示最大按键为Button 3。实际上第二和第三行表示了一共有三个按键:Button 1 ~ Button3。
  4. LOGICAL_MINIMUM (0)表示按键状态的最小值为 0。
  5. LOGICAL_MAXIMUM (1)表示按键状态的最大值为 1。
  6. REPORT_COUNT (3)表示数量为 3。
  7. REPORT_SIZE (1)表示占用空间,单位为 bit,1 表示占用空间为 1-bit。
  8. INPUT (Data,Var,Abs)表示以上 3-bit 数据为发送给电脑的数据,Data表示这个数据是可变的,由用户决定。6、7、8三行代码表明:表示三个按钮状态的数据占用 3-bit 的空间,分别是 bit0 ~ bit2,数据是可变的。
  9. REPORT_COUNT (1)表示数量 1。
  10. REPORT_SIZE (5)表示占用空间为 5-bit。
  11. INPUT (Cnst,Var,Abs)表示以上 5-bit 数据为发送给上位机的固定数据,Cnst即为 Const 常量的意思。9、10、11行代码表示还有 5-bit 的不可变数据。即图 1 中第一个字节中的 5 个 bit 的不使用数据。
  12. USAGE_PAGE (Generic Desktop)表示开始描述一个新的数据了。
  13. USAGE (X)表示描述的是 X 坐标,在USAGE_PAGE中选择了Generic Desktop后,双击【USAGE】项则可以选择【X】。
  14. USAGE (Y)表示描述的是 Y 坐标,由于 X、Y 坐标格式一致,所以放在一起描述了,这是省略的写法,完全可以单独定义 Y 坐标数据。
  15. LOGICAL_MINIMUM (-127)表示最小值为 -127。
  16. LOGICAL_MAXIMUM (127)表示最大值为 127。我们知道一个字节可存储的有符号整数范围为-128~127。这里只是为了对称,将最小值设为 -127。
  17. REPORT_SIZE (8)表示以上 X、Y数据占用 8-bit 空间。
  18. REPORT_COUNT (2) 数量为 2。
  19. INPUT (Data,Var,Rel)表示以上2个字节是发送给上位机的可变数据,Rel表示这是相对值。

需要注意,你可能会疑惑,表示中键滚轮滚动状态的数据存放在哪?我用工具测了下我用的鼠标,实际上鼠标 Report 的长度为 6 个字节。上面的可能是最早期鼠标的报告描述符。

键盘报告描述符

接下来在 HID Descriptor Tool 中,打开【keybrd.hid】文件,这是标准的键盘报告描述符,如下图所示:

图 3:键盘报告描述符

理解了上述鼠标报告描述符,再读这个就简单很多了。这次就不讲这么详细了,分块讲解。

键盘数据包由 8 个字节组成,每个字节的意义如下图所示:

图 4:键盘数据包结构

如果不理解上述表格,我们来讲个例子,假设你按下了【左Ctrl + A + B】键,则键盘应当向上位机发送如下数据,红色部分对应三个按键:

图 5:按键数据示例

我写键盘代码的时候还没把这块理解得这么详细,这回算是补齐这块短板了。另外,由此数据格式可知,那些号称全键无种的键盘完全是逗你玩,没有任何意义,键盘每次发送的数据最多只能容纳 8 个功能键加 6 个普通键共 14 个按键,而 8 个功能键意义重复,只相当于 4 个按键。所以 10 键无冲才是靠谱的说法。

下面讲述键盘报告描述符中的编码:

USAGE_PAGE (Generic Desktop)	05 01
USAGE (Keyboard)	09 06
COLLECTION (Application)	A1 01 
  ......
END_COLLECTION	C0

上述代码表示这是一个键盘设备。

USAGE_PAGE (Keyboard)	05 07
USAGE_MINIMUM (Keyboard LeftControl)	19 E0
USAGE_MAXIMUM (Keyboard Right GUI)	29 E7
LOGICAL_MINIMUM (0)	15 00 
LOGICAL_MAXIMUM (1)	25 01 
REPORT_SIZE (1)	75 01 
REPORT_COUNT (8)	95 08 
INPUT (Data,Var,Abs)	81 02 

上述代码描述了第一个字节中的 8 个位的含义。

REPORT_COUNT (1)	95 01 
REPORT_SIZE (8)	75 08 
INPUT (Cnst,Var,Abs)	81 03 

上述三行代码描述了第二个字节为固定数据,即为保留数据。

REPORT_COUNT (5)	95 05 
REPORT_SIZE (1)	75 01 
USAGE_PAGE (LEDs)	05 08
USAGE_MINIMUM (Num Lock)	19 01
USAGE_MAXIMUM (Kana)	29 05
OUTPUT (Data,Var,Abs)	91 02 
REPORT_COUNT (1)	95 01 
REPORT_SIZE (3)	75 03 
OUTPUT (Cnst,Var,Abs)	91 03 

上述代码为由上们机发向键盘的数据描述,由一个字节组成,结构如下图所示:

图 6:输出数据

今天我才知道原来上位机还可以控制键盘,不过这也得键盘支持才行,没有什么意义,无视。

REPORT_COUNT (6)	95 06 
REPORT_SIZE (8)	75 08 
LOGICAL_MINIMUM (0)	15 00 
LOGICAL_MAXIMUM (101)	25 65 
USAGE_PAGE (Keyboard)	05 07
USAGE_MINIMUM (Reserved (no event indicated))	19 00
USAGE_MAXIMUM (Keyboard Application)	29 65
INPUT (Data,Ary,Abs)	81 00 

上述代码描述了剩余 6 个字节的含义,即每个字节表述一个键值,键值 0 表示没有按键,其它的键码可在 HID Descriptor Tool 中查到,双击【USAGE_MINIMUM】项就可以看到了。

OK,好久没写文章了,又泡制了一篇,有了以上知识,我们就可以开始模拟一个简单的鼠标或键盘了。

;

© 2018 - IOT小分队文章发布系统 v0.3